/*! JavaScript Hooker - v0.2.3 - 1/22/2012
* http://github.com/cowboy/javascript-hooker
* Copyright (c) 2012 "Cowboy" Ben Alman; Licensed MIT
* 易支烟修改版
* hook(对象,Hook属性,Hook方法,是否在调用原函数之前调用)
* unhook(对象,Hook属性)
* */
(function(exports) {
    // Get an array from an array-like object with slice.call(arrayLikeObject).
    var slice = [].slice;
    // Get an "[object [[Class]]]" string with toString.call(value).
    var toString = {}.toString;

    // I can't think of a better way to ensure a value is a specific type other
    // than to create instances and use the `instanceof` operator.
    function HookerOverride(v) { this.value = v; }
    function HookerPreempt(v) { this.value = v; }
    function HookerFilter(c, a) { this.context = c; this.args = a; }

    // When a pre- or post-hook returns the result of this function, the value
    // passed will be used in place of the original function's return value. Any
    // post-hook override value will take precedence over a pre-hook override
    // value.
    exports.override = function(value) {
        return new HookerOverride(value);
    };

    // When a pre-hook returns the result of this function, the value passed will
    // be used in place of the original function's return value, and the original
    // function will NOT be executed.
    exports.preempt = function(value) {
        return new HookerPreempt(value);
    };

    // When a pre-hook returns the result of this function, the context and
    // arguments passed will be applied into the original function.
    exports.filter = function(context, args) {
        return new HookerFilter(context, args);
    };

    // Execute callback(s) for properties of the specified object.
    function forMethods(obj, props, callback) {
        var prop;
        if (typeof props === "string") {
            // A single prop string was passed. Create an array.
            props = [props];
        } else if (props == null) {
            // No props were passed, so iterate over all properties, building an
            // array. Unfortunately, Object.keys(obj) doesn't work everywhere yet, so
            // this has to be done manually.
            props = [];
            for (prop in obj) {
                if (obj.hasOwnProperty(prop)) {
                    props.push(prop);
                }
            }
        }
        // Execute callback for every method in the props array.
        var i = props.length;
        while (i--) {
            // If the property isn't a function...
            if (toString.call(obj[props[i]]) !== "[object Function]" ||
                // ...or the callback returns false...
                callback(obj, props[i]) === false) {
                // ...remove it from the props array to be returned.
                props.splice(i, 1);
            }
        }
        // Return an array of method names for which the callback didn't fail.
        return props;
    }

    // Monkey-patch (hook) a method of an object.
    exports.hook = function(obj, props, options,before=true) {
        // 如果props参数被省略，则对参数进行无序排列。
        if (options == null) {
            options = props;
            props = null;
        }
        // 如果只传递一个函数而不传递选项哈希，则将其用作
        // pre-hook function.
        if (typeof options === "function") {
            options = {pre: options};
        }

        // Hook the specified method of the object.
        return forMethods(obj, props, function(obj, prop) {
            // The original (current) method.
            var orig = obj[prop];
            // The new hooked function.
            function hooked() {
                var result, origResult, tmp;

                // Get an array of arguments.
                var args = slice.call(arguments);

                // If passName option is specified, prepend prop to the args array,
                // passing it as the first argument to any specified hook functions.
                if (options.passName) {
                    args.unshift(prop);
                }

                // 在调用函数之前调用hook函数
                if (before===true&&options.pre) {
                    result = options.pre.apply(this, args);
                    origResult = orig.apply(this, arguments);
                }else{
                    origResult = orig.apply(this, arguments);
                    result = options.pre.apply(origResult, args);
                }
                result = result? result : origResult;
                /*        if (result instanceof HookerFilter) {
                          // If the pre-hook returned hooker.filter(context, args), invoke the
                          // original function with that context and arguments, and store its
                          // result.
                          origResult = result = orig.apply(result.context, result.args);
                        } else if (result instanceof HookerPreempt) {
                          // If the pre-hook returned hooker.preempt(value) just use the passed
                          // value and don't execute the original function.
                          origResult = result = result.value;
                        } else {
                          // Invoke the original function in the current context with the
                          // passed-in arguments, and store its result.

                          //origResult = orig.apply(this, arguments);

                          // If the pre-hook returned hooker.override(value), use the passed
                          // value, otherwise use the original function's result.

                        }*/

                if (options.post) {
                    // If a post-hook function was specified, invoke it in the current
                    // context, passing in the result of the original function as the
                    // first argument, followed by any passed-in arguments.
                    tmp = options.post.apply(this, [origResult].concat(args));
                    if (tmp instanceof HookerOverride) {
                        // If the post-hook returned hooker.override(value), use the passed
                        // value, otherwise use the previously computed result.
                        result = tmp.value;
                    }
                }

                // Unhook if the "once" option was specified.
                if (options.once) {
                    exports.unhook(obj, prop);
                }

                // Return the result!
                return result;
            }
            // Re-define the method.
            obj[prop] = hooked;
            // Fail if the function couldn't be hooked.
            if (obj[prop] !== hooked) { return false; }
            // Store a reference to the original method as a property on the new one.
            obj[prop]._orig = orig;
        });
    };

    // Get a reference to the original method from a hooked function.
    exports.orig = function(obj, prop) {
        return obj[prop]._orig;
    };

    // Un-monkey-patch (unhook) a method of an object.
    exports.unhook = function(obj, props) {
        return forMethods(obj, props, function(obj, prop) {
            // Get a reference to the original method, if it exists.
            var orig = exports.orig(obj, prop);
            // If there's no original method, it can't be unhooked, so fail.
            if (!orig) { return false; }
            // Unhook the method.
            obj[prop] = orig;
        });
    };
    //请求
    exports.JSONPHook=function(urls,hookCallBack,callName='callback'){
        function isExit(url){
            let ret=false;
            urls.forEach((e)=>{
                if(url.indexOf(e)!==-1){
                    ret=true;
                }
            });
            return ret;
        }
        exports.hook(document, "createElement", function(e) {
            let tag=this;
            if(e==='script'){
                Object.defineProperty(tag, 'src', {
                    configurable: true,
                    enumerable: true,
                    set: function(n) {
                        //拿到链接，判断是为hook链接
                        if(isExit(n)){
                            // 设置callback
                           try{
                               let baseCallbackName=eval("(n.match(/"+callName+"=[\\s\\S]*?&|"+callName+"=[\\s\\S]*/)[0])").replace(callName+'=','');
                               if(baseCallbackName[baseCallbackName.length-1]==='&')baseCallbackName=baseCallbackName.substring(0,baseCallbackName.length-1);
                               let callbackName='jsonp'+new Date().getTime();
                               window[callbackName]=function(json){
                                   let args = [].slice.call(arguments);
                                   //调用callback
                                   if(hookCallBack)hookCallBack.apply(tag,args);
                                   //调用返回的
                                   if(typeof window[baseCallbackName]==='function')window[baseCallbackName].apply(tag,args);
                                   window[callbackName]=undefined;
                               };
                               n=n.replace(baseCallbackName,callbackName);
                           }catch (e) {}
                        }
                        //给真正的tag赋值
                        this.setAttribute('src',n)
                    },
                    get:function(){
                        return this.getAttribute('src');
                    }
                });
            }
            return tag;
        },false);
    };
}(typeof exports === "object" && exports || this));


//hook(object object,string hook,function hookCallBack,boolean before);
//unhook(object object,string hook);
//JSONPHook([] urls,function callback,string callbackName);

